package jgf;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;
import jgf.exceptions.arc.DuplicatedArcException;
import jgf.exceptions.arc.UnexistentArcException;
import jgf.exceptions.attribute.AttributeException;
import jgf.exceptions.attribute.DuplicatedAttributeException;
import jgf.exceptions.attribute.UnexistentAttributeException;

/**
 * This class implements the funcionality of the node of a graph.
 * @author Marcos Garcia Ortega <a href="mailto:thiamath@gmail.com">&lt;thiamath@gmail.com&gt;</a>
 */
public class Node implements Serializable{

	////////////
	// Fields //
	////////////

	// <editor-fold defaultstate="collapsed" desc="Fields">
	/** Identificates univocally this node in a graph. */
	private Integer id;
	/** The label assigned to this node. */
	protected String label;
	/** A map of the attributes of the arc. */
	protected TreeMap<String, Attribute> attributes;
	/**
	 * A map of the arcs that are linked to this node.
	 * The mapping is made by its ID numbers.
	 */
	private TreeMap<Integer, Arc> linkedArcs;
	// </editor-fold>

	/////////////////////
	// Node management //
	/////////////////////

	// <editor-fold defaultstate="collapsed" desc="Constructors">
	/**
	 * Constructor that creates a node without label and an invlid ID number.
	 * Should never be used.
	 */
	protected Node() {
		this(-1, "");
	}

	/**
	 * Creates a node with an ID number. The label and the attributes
	 * will be empty.
	 * @param ID The ID number of the node assigned by the owner graph.
	 */
	public Node(Integer ID) {
		this(ID, "");
	}

	/**
	 * Creates a node with an ID number and a label. The attribute map
	 * will be empty.
	 * @param ID The ID number of the node assigned by the owner graph.
	 * @param label The label for the node.
	 */
	public Node(Integer ID, String label) {
		this(ID, label, null);
	}

	/**
	 * Creates a node with an ID number and a label and a list of attributes.
	 * @param ID The ID number of the node assigned by the owner graph.
	 * @param label The label for the node.
	 * @param attributes The attributes for this node.
	 */
	public Node(Integer ID, String label, List<Attribute> attributes) {
		if (ID == null) {//If the ID is null
			throw new NullPointerException("A ID must be assigned to the node.");//Throw a NullPointerException.
		}
		this.id = ID;//Assign the given ID number to the node.
		if (label == null) {//If the label is null
			this.label = "";//put an empty string as label.
		} else {//else
			this.label = label;//put the given label on the node.
		}
		this.attributes = new TreeMap<String, Attribute>();//Initialice the attribute map.
		if (attributes != null) {//If the given list of attributes is not null
			for (Attribute attribute : attributes) {//For each given attribute
				this.attributes.put(attribute.getName(), attribute);//Put it into the map.
			}
		}
		linkedArcs = new TreeMap<Integer, Arc>();//Initialice the map af linked arcs.
	}
	// </editor-fold>

	// <editor-fold defaultstate="collapsed" desc="Getters">
	/**
	 * Gets the label of the arc.
	 * @return A String with the label of the arc.
	 */
	public String getLabel() {
		return label;
	}

	/**
	 * Gets the ID number of this node.
	 * @return A String with the ID number of the node.
	 */
	public Integer getId() {
		return id;
	}
	// </editor-fold>

	// <editor-fold defaultstate="collapsed" desc="Setters">
	/**
	 * Sets the label of the node.
	 * @param label A String with the new label for the node.
	 */
	public void setLabel(String label) {
		this.label = label;
	}
	// </editor-fold>

	// <editor-fold defaultstate="collapsed" desc="Linked arcs">
	/**
	 * Gets a list with all the arcs linked to this node.
	 * @return A List of Arc with all the linked arcs of the node.
	 */
	public List<Arc> getLinkedArcs() {
		ArrayList<Arc> ret = new ArrayList<Arc>();
		
		for (Integer key : this.linkedArcs.keySet()) {
			ret.add(this.linkedArcs.get(key));
		}
		
		return ret;
	}

	/**
	 * Inserts a new arc into the list of linked arcs.
	 * @param arc The arc that has been linked to this node.
	 * @throws DuplicatedArcException If the arc is already linked to the node,
	 *	this exception will raise.
	 */
	public void insertLinkedArc(Arc arc) throws DuplicatedArcException {
		if(linkedArcs.containsKey(arc.getId()))//If the arc is already in the list of linked arcs
			throw new DuplicatedArcException(this.getClass(),
					"insertLinkedArc",
					new String[]{"arc = " + arc});//Throw the DuplicatedArcException.
		linkedArcs.put(arc.getId(), arc);//Put the arc into the list.
	}

	/**
	 * Removes the arc selected by its ID number from the list of linked arcs.
	 * @param arc An Integer with the ID number of the arc to be removed.
	 * @throws UnexistentArcException If the given ID number do not correspond
	 *	with any linked arc, this exception will raise.
	 */
	public void removeLinkedArc(Integer arcID) throws UnexistentArcException {
		if(arcID == null)
			throw new NullPointerException("The ID number can not be null.");
		if(linkedArcs.remove(arcID) == null)
			throw new UnexistentArcException(this.getClass(),
					"removeLinkedArc",
					new String[]{"arcID = " + arcID});
	}

	/**
	 * Clears the list of linked arcs (the node will be released of arcs).
	 */
	public void clearLinkedArcs() {
		linkedArcs.clear();
	}
	// </editor-fold>

	//////////////////////////
	// Attribute management //
	//////////////////////////

	// <editor-fold defaultstate="collapsed" desc="Attribute management">
	/**
	 * Add a new attribute to the arc, with the name "name" and the value "value".
	 * @param attribute The attribute to insert into the node.
	 */
	public void insertAttribute(Attribute attribute) {
		attributes.put(attribute.getName(), attribute);
	}

	/**
	 * Erase the attribute with name "attributeName" from the arc.
	 * @param attributeName The name of the attribute.
	 */
	public void removeAttribute(String attributeName) {
		attributes.remove(attributeName);
	}

	/**
	 * Changes the name of a node attribute to a new name and changes also
	 * its default value.
	 * @param previousName A String with the previous name of the attribute.
	 * @param newAttribute An Attribute with the new attribute name and value.
	 * @throws AttributeException
	 * @throws DuplicatedAttributeException If the new name already exists
	 *	on the list of default attributes for nodes, this exception will raise.
	 * @throws UnexistentAttributeException If the previous name does not exist
	 *	on the list of default attributes for nodes, this exception will raise.
	 */
	public void updateAttribute(String previousName, Attribute newAttribute)
			throws AttributeException,
			UnexistentAttributeException,
			DuplicatedAttributeException {
		if (previousName == null || newAttribute == null) {//If the previous name or the new attribute are null
			throw new AttributeException(this.getClass(),
					"updateDefaultArcAttribute",
					new String[]{"previousName = " + previousName,
						"newAttribute = " + newAttribute});//Throw a AttributeException
		}
		if (!attributes.containsKey(previousName)) {//If the previous attribute do not exist
			throw new UnexistentAttributeException(this.getClass(),
					"updateDefaultArcAttribute",
					new String[]{"previousName = " + previousName,
						"newName = " + newAttribute});//Throw a UnexistentAttributeException
		}
		if (!previousName.equalsIgnoreCase(newAttribute.getName())) {//If the new name is different than the previous one
			if (attributes.containsKey(newAttribute.getName())) {
				throw new DuplicatedAttributeException(this.getClass(),
						"updateDefaultArcAttribute",
						new String[]{"previousName = " + previousName,
							"newAttribute = " + newAttribute});//Throw a DuplicatedAttributeException
			} else {
				attributes.remove(previousName);//remove the previous attribute
			}
		}
		attributes.put(newAttribute.getName(), newAttribute);//Put or replace the new attribute
	}

	/**
	 * Put replace the list of attributes into the node.
	 * @param attributes The list of attributes to be included in the node.
	 */
	public void setAttributeList(List<Attribute> attributes) {
		this.attributes.clear();
		for (Attribute attribute : attributes) {
			this.attributes.put(attribute.getName(), attribute);
		}
	}

	/**
	 * Gets the whole list of attributes of the node.
	 * @return A List of Attribute with all the attributes of the node.
	 */
	public List<Attribute> getAttributeList() {
		List<Attribute> ret = new ArrayList<Attribute>();

		for (String key : attributes.keySet()) {
			ret.add(attributes.get(key));
		}

		return ret;
	}

	/**
	 * Returns the attribute with name "name".
	 * @param name The name of the attribute.
	 * @return The selected Attribute.
	 * @throws UnexistentAttributeException If the node do not contain
	 *	the attribute, this exception will rise.
	 */
	public Attribute getAttribute(String name) throws UnexistentAttributeException {
		Attribute value = attributes.get(name);
		if (value == null) {
			throw new UnexistentAttributeException(this.getClass(),
					"getAttribute",
					new String[]{"name = " + name});

		}
		return value;
	}
	// </editor-fold>
}
